#include <common/define.h>
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/thread/thread.hpp>
#include <common/EventBase.h>
#include <common/Network.h>
#include <flashpp/Flashpp.h>
#include <folly/logging/Init.h>
#include <folly/logging/xlog.h>
#include <folly/system/ThreadName.h>
#include <gflags/gflags.h>
#include <yaml-cpp/yaml.h>
#include <csignal>
#include <fstream>
#include <iostream>
#include <thread>


using namespace flashpp;

DEFINE_string(ds, "", "The data plane dataset for evaluation");

FOLLY_INIT_LOGGING_CONFIG(".=INFO:y,folly=INFO:y; default:async=true,sync_level=WARNING;y=file:path=out.log");

std::vector<std::string>
split(std::string const& str, char delim) {
  std::vector<std::string> tokens;
  size_t start;
  size_t end = 0;
  while ((start = str.find_first_not_of(delim, end)) != std::string::npos) {
    end = str.find(delim, start);
    tokens.push_back(str.substr(start, end - start));
  }
  return tokens;
}

void
loadFIB(Device& device, std::string filename, flashpp::DataPlaneUpdate& dpu) {
  std::fstream file;
  file.open(filename, std::ios::in);
  if (!file.is_open()) {
    return;
  }
  std::string line;
  while (std::getline(file, line)) {
    std::vector<std::string> tokens;
    boost::split(tokens, line, boost::is_any_of(" "));
    if (tokens[0] != "fw") {
      continue;
    }
    std::string eport;
    int priority;
    if (tokens.size() == 4) {
      priority = std::stoi(tokens[2]);
      eport = split(tokens[3], '.')[0];
    } else {
      priority = std::stoi(tokens[3]);
      eport = split(tokens[4], '.')[0];
    }
    auto& port = device.getCreatePort(eport);
    auto addr = (uint32_t)std::atoi(tokens[1].c_str());
    auto rule = std::make_shared<Rule>();
    rule->addr = addr;
    rule->bits = addr;
    rule->prefixLength = std::stoi(tokens[2]);
    rule->priority = priority;
    rule->port = &port;
    device.addRule(rule);
    // Rule rule = Rule{
    //     .addr = addr,
    //     .bits = addr,
    //     .prefixLength = std::stoi(tokens[2]),
    //     .priority = priority,
    //     .port = &port};
    dpu.added.emplace_back(rule);
  }
  std::sort(
      dpu.added.begin(),
      dpu.added.end(),
      [&](auto a, auto b) {
        return a->priority > b->priority;
      });
}

void
loadDataset(
    std::string datasetDir,
    flashpp::Network& network,
    std::vector<std::shared_ptr<DataPlaneUpdate>>& dpus) {
  YAML::Node spec = YAML::LoadFile(datasetDir + "/spec.yaml");
  network.setName(spec["name"].as<std::string>());
  auto topoSpec = spec["topo"];
  if (topoSpec["format"].as<std::string>() == "plain") {
    auto topoFile = datasetDir + "/" + topoSpec["path"].as<std::string>();
    std::fstream fs(topoFile);
    if (!fs) {
      throw std::runtime_error("Cannot open topo: " + topoFile);
    }
    std::string line;
    while (std::getline(fs, line)) {
      std::vector<std::string> tokens;
      boost::split(tokens, line, boost::is_any_of(" "));

      network.addLink(tokens[0], tokens[1], tokens[2], tokens[3]);
    }

    // load FIB
    auto fibSpec = spec["fib"];
    for (auto& ele : network.nameToDevice()) {
      std::string filename =
          fibSpec["filename"] ? fibSpec["filename"].as<std::string>() : "$";
      boost::replace_all(filename, "$", ele.first);
      auto fibFile =
          datasetDir + "/" + fibSpec["path"].as<std::string>() + "/" + filename;
      auto dpu = std::make_shared<DataPlaneUpdate>();
      dpu->isSameDevice = true;
      dpu->deviceName = ele.first;
      loadFIB(ele.second, fibFile, *dpu);
      dpus.push_back(dpu);
    }
  } else {
    throw std::runtime_error("Invalid topo format");
  }
}
#ifdef USE_MTBDD
std::vector<sylvan::Bdd> HeaderSpace::vars;
#endif

int
main(int argc, char** argv) {
  gflags::ParseCommandLineFlags(&argc, &argv, true);
  std::cout << "Greetings!" << std::endl;

  folly::EventBase signalHandlerEvb;
  flashpp::EventBaseStopSignalHandler handler(&signalHandlerEvb);
  // Starting signalHandler eventbase to receive system signal
  std::thread signalHandlerEvbThread([&]() noexcept {
    XLOG(INFO) << "Starting signal handler evb...";
    folly::setThreadName("signal");
    signalHandlerEvb.loopForever();
    XLOG(INFO) << "Signal handler evb stopped.";
  });
  signalHandlerEvb.waitUntilRunning();

  auto network = Network();
  auto flashpp = flashpp::Flashpp(network);
  flashpp.run();

  std::vector<std::shared_ptr<DataPlaneUpdate>> dpus;
  loadDataset(FLAGS_ds, network, dpus);

  XLOG(INFO) << "Start";
  for (auto& dpu : dpus) {
    flashpp.push(dpu);
    // break;
  }

  signalHandlerEvbThread.join();

  return 0;
}